package internal

import (
	"context"
	"fmt"
	"net/url"
	"strconv"
	"time"

	"github.com/gomodule/redigo/redis"
)

//New
//dns:
//redis://{username}:{password}@{host}:{port:<int>}/<db>?
//idle_timeout=<duration>&
//max_idle=<int>&
//max_active=<int>&
//wait=<true/false>&
//max_conn_lifetime=<duration>
func New(dsn string) Pool {
	rUri, err := url.Parse(dsn)
	if err != nil {
		return ErrPool(err)
	}

	var (
		host               = rUri.Hostname()
		port, _            = strconv.Atoi(rUri.Port())
		username           = rUri.User.Username()
		password, _        = rUri.User.Password()
		db, _              = strconv.Atoi(rUri.Path)
		query              = rUri.Query()
		idleTimeout, _     = time.ParseDuration(query.Get("idle_timeout"))
		maxIdle, _         = strconv.Atoi(query.Get("max_idle"))
		MaxActive, _       = strconv.Atoi(query.Get("max_active"))
		wait, _            = strconv.ParseBool(query.Get("wait"))
		maxConnLifetime, _ = time.ParseDuration(query.Get("max_conn_lifetime"))
	)

	rp := &redis.Pool{
		DialContext:     dial(host, port, username, password, db),
		TestOnBorrow:    testOnBorrow,
		IdleTimeout:     idleTimeout,
		MaxIdle:         maxIdle,
		MaxActive:       MaxActive,
		Wait:            wait,
		MaxConnLifetime: maxConnLifetime,
	}

	return &pool{Pool: rp}
}

func dial(host string, port int, username, password string, db int) func(ctx context.Context) (redis.Conn, error) {
	return func(ctx context.Context) (c redis.Conn, err error) {
		defer func() {
			if err != nil && c != nil {
				_ = c.Close()
			}
		}()

		c, err = redis.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", host, port))
		if err != nil {
			return
		}
		if password != "" {
			var autArgs []interface{}
			if username != "" {
				autArgs = []interface{}{username, password}
			} else {
				autArgs = []interface{}{password}
			}
			if _, err = c.Do("AUTH", autArgs...); err != nil {
				return
			}
		} else {
			if _, err = c.Do("NOAUTH"); err != nil {
				return
			}
		}
		if _, err = c.Do("SELECT", db); err != nil {
			return
		}
		return
	}
}

func testOnBorrow(c redis.Conn, t time.Time) error {
	if time.Since(t) < time.Minute {
		return nil
	}
	_, err := c.Do("PING")
	return err
}
